﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Interpreter.BuiltIns
{
	internal class SystemPrimitiveMethods : Module
	{
		public override void Register(FunctionRegistry registry)
		{
			registry.Register(26, new ListAdd());
			registry.Register(32, new ListAddList());
			registry.Register(27, new ListClear());
			registry.Register(33, new ListInsert());
			registry.Register(36, new ListJoin());
			registry.Register(34, new ListReverse());
			registry.Register(28, new MapAdd());
			registry.Register(37, new MapClear());
			registry.Register(31, new MapContains());
			registry.Register(35, new MapGet());
			registry.Register(41, new StringSplit());
			registry.Register(42, new StringSplitWhitespace());
			registry.Register(45, new StringTrim());
			registry.Register(46, new StringTrimChars());
			registry.Register(51, new FastListAdd());
			registry.Register(52, new ListRemove());
		}

		private class FastListAdd : NativeFunction
		{
			// The purpose of this native function is to add n values to a list in one swoop.
			// Each drawn entity of the Game.Screen results in about 5 or 6 calls to list.add(item), which
			// itself is B Flat interpreted code that calls a B Flat function which each make a similar call like
			// this 6 times. This should save a lot of CPU. 
			public override Value Do(StackContext stackContext, Value[] args)
			{
				Value[] list = (Value[])args[0].Value0;
				int listVirtualLength = (int)args[0].Value1;
				int realLength = list.Length;
				int argsLength = args.Length;
				while (realLength < argsLength - 1 + listVirtualLength)
				{
					Value[] newList = new Value[list.Length * 2];
					System.Array.Copy(list, newList, listVirtualLength);
					list = newList;
					realLength = newList.Length;
					args[0].Value0 = list;
				}

				int targetIndex = listVirtualLength;

				for (int i = 1; i < argsLength; ++i)
				{
					list[targetIndex++] = args[i];
				}

				args[0].Value1 = targetIndex;

				return Value.Null;
			}
		}

		private class StringTrim : NativeFunction
		{
			public override Value Do(StackContext stackContext, Value[] args)
			{
				Value inputString = stackContext.ClassContext;
				string value = (string)inputString.Value0;
				string newValue = value.Trim();
				if (value != newValue)
				{
					return new Value(Value.TYPE_STRING, newValue);
				}
				return inputString;
			}
		}

		private class StringTrimChars : NativeFunction
		{
			public override Value Do(StackContext stackContext, Value[] args)
			{
				Value inputString = stackContext.ClassContext;
				string value = (string)inputString.Value0;
				string newValue = value.Trim(((string)args[0].Value0).ToCharArray());
				if (value != newValue)
				{
					return new Value(Value.TYPE_STRING, newValue);
				}
				return inputString;
			}
		}

		private static Value CreateStringList(string[] items)
		{
			int count = items.Length;
			Value[] output = new Value[count];
			for (int i = count - 1; i >= 0; --i)
			{
				output[i] = new Value(Value.TYPE_STRING, items[i]);
			}

			return new Value(Value.TYPE_LIST, output, count);
		}

		private static StringSplitOptions DEFAULT_SPLIT_OPTIONS = new StringSplitOptions();
		private class StringSplit : NativeFunction
		{
			public override Value Do(StackContext stackContext, Value[] args)
			{
				string stringValue = stackContext.ClassContext.Value0.ToString();
				string delimiter = stackContext.Args[0].Value0.ToString();
				string[] pieces = stringValue.Split(new string[] { delimiter }, DEFAULT_SPLIT_OPTIONS);
				return CreateStringList(pieces);
			}
		}

		private class StringSplitWhitespace : NativeFunction
		{
			public override Value Do(StackContext stackContext, Value[] args)
			{
				string stringValue = stackContext.ClassContext.Value0.ToString();
				string[] pieces = stringValue.Split(' ', '\t', '\n', '\r'); // TODO: check "foo[space][space]bar" --> ["foo", "bar"], not ["foo", "", "bar"]
				return CreateStringList(pieces);
			}
		}

		private class MapClear : NativeFunction
		{
			public override Value Do(StackContext stackContext, Value[] args)
			{
				MapImpl map = (MapImpl)stackContext.ClassContext.Value0;
				map.Clear();
				return Value.Null;
			}
		}

		private class MapAdd : NativeFunction
		{
			public override Value Do(StackContext stackContext, Value[] args)
			{
				MapImpl map = (MapImpl)stackContext.ClassContext.Value0;
				if (map.TryAdd(args[0], args[1], false))
				{
					return Value.Null;
				}
				else
				{
					// TODO: return an exception
					return Value.Null;
				}
			}
		}

		private class ListRemove : NativeFunction
		{
			public override Value Do(StackContext stackContext, Value[] args)
			{
				Value[] items = (Value[])stackContext.ClassContext.Value0;
				int size = (int)stackContext.ClassContext.Value1;
				int index = (int)stackContext.Args[0].Value0;
				if (index < 0 || index >= size)
				{
					// TODO: return an exception
				}
				else
				{
					for (int i = index; i < size - 1; ++i)
					{
						items[i] = items[i + 1];
					}

					items[size - 1] = null;
					stackContext.ClassContext.Value1 = size - 1;
				}
				return Value.Null;
			}
		}

		private class ListReverse : NativeFunction
		{
			public override Value Do(StackContext stackContext, Value[] args)
			{
				Value[] items = (Value[])stackContext.ClassContext.Value0;
				int size = (int)stackContext.ClassContext.Value1;
				int mid = size / 2;
				Value t;
				int antiI = size - 1;
				for (int i = 0; i < mid; ++i)
				{
					t = items[i];
					items[i] = items[antiI];
					items[antiI] = t;
					--antiI;
				}

				return Value.Null;
			}
		}

		private class ListJoin : NativeFunction
		{
			public override Value Do(StackContext stackContext, Value[] args)
			{
				string delimiter = stackContext.Args.Length == 0 ? null : stackContext.Args[0].Value0.ToString();
				Value[] items = (Value[])stackContext.ClassContext.Value0;

				if (items.Length == 0)
				{
					return Value.EMPTY_STRING;
				}

				string[] output = new string[items.Length];
				for (int i = 0; i < items.Length; ++i)
				{
					output[i] = items[i].ForceString(stackContext.Metadata);
				}

				return new Value(Value.TYPE_STRING, string.Join(delimiter ?? string.Empty, output));
			}
		}

		private class ListInsert : NativeFunction
		{
			public override Value Do(StackContext stackContext, Value[] args)
			{
				int index = (int)stackContext.Args[0].Value0;
				Value value = stackContext.Args[1];
				Value list = stackContext.ClassContext;
				Value[] array = (Value[])list.Value0;
				int size = (int)list.Value1;

				if (index < 0 || index > size)
				{
					// TODO: throw out of bounds exception
				}
				
				int i;
				if (array.Length == size)
				{
					Value[] newArray = new Value[size * 2 + 1];
					for (i = 0; i < index; ++i)
					{
						newArray[i] = array[i];
					}
					newArray[index] = value;
					for (i = index; i < size; ++i)
					{
						newArray[i + 1] = array[i];
					}
					value.Value0 = newArray;
				}
				else
				{
					for (i = size - 1; i >= index; --i)
					{
						array[i + 1] = array[i];
					}
					array[index] = value;
				}
				value.Value1 = size + 1;

				return Value.Null;
			}
		}

		private class ListAddList : NativeFunction
		{
			public override Value Do(StackContext stackContext, Value[] args)
			{
				Value baseList = stackContext.ClassContext;
				Value newList = stackContext.Args[0];
				Value[] baseListArray = (Value[])baseList.Value0;
				Value[] newListArray = (Value[])newList.Value0;
				int leftSize = (int)baseList.Value1;
				int rightSize = (int)baseList.Value2;
				int newSize = leftSize + rightSize;
				if (newSize > baseListArray.Length)
				{
					int newCapcity = baseListArray.Length * 2 + 1;
					if (newCapcity < newSize)
					{
						newCapcity = newSize;
					}

					Value[] targetArray = new Value[newCapcity];
					for (int i = 0; i < leftSize; ++i)
					{
						targetArray[i] = baseListArray[i];
					}
					for (int i = 0; i < rightSize; ++i)
					{
						targetArray[leftSize + i] = newListArray[i];
					}
					baseList.Value0 = targetArray;
				}
				else
				{
					for (int i = 0; i < rightSize; ++i)
					{
						baseListArray[leftSize + i] = newListArray[i];
					}
				}
				baseList.Value1 = newSize;
				
				return Value.Null;
			}
		}

		private class MapContains : NativeFunction
		{
			public override Value Do(StackContext stackContext, Value[] args)
			{
				Value key = args[0];
				Value mapValue = stackContext.ClassContext;
				Bucket b = ((MapImpl)mapValue.Value0).Get(key, key.ValueHash());
				return b == null ? Value.False : Value.True;
			}
		}

		private class MapGet : NativeFunction
		{
			public override Value Do(StackContext stackContext, Value[] args)
			{
				Value key = args[0];
				Value defaultValue = args[1];
				Value mapValue = stackContext.ClassContext;
				Bucket b = ((MapImpl)mapValue.Value0).Get(key, key.ValueHash());
				if (b != null)
				{
					return b.Value;
				}
				return defaultValue;
			}
		}

		private class ListAdd : NativeFunction
		{
			public override Value Do(StackContext stackContext, Value[] args)
			{
				Value list = stackContext.ClassContext;
				Value[] array = (Value[])list.Value0;
				int oldSize = (int)list.Value1;
				if (array.Length == oldSize)
				{
					int newSize = oldSize < 5 ? 10 : (oldSize * 2 + 1);

					Value[] newArray = new Value[newSize];
					for (int i = oldSize - 1; i >= 0; --i)
					{
						newArray[i] = array[i];
					}
					list.Value0 = newArray;
					array = newArray;
				}
				array[oldSize] = args[0];
				list.Value1 = oldSize + 1;
				return Value.Null;
			}
		}

		private class ListClear : NativeFunction
		{
			// leave the allocation at the same size since it is likely to return to the same order of magnitude at some point
			public override Value Do(StackContext stackContext, Value[] args)
			{
				Value list = stackContext.ClassContext;
				Value[] array = (Value[])list.Value0;
				for (int i = (int)list.Value1 - 1; i >= 0; --i)
				{
					array[i] = null;
				}
				list.Value1 = 0;
				return Value.Null;
			}
		}
	}
}
